通过 PulseAudio 播放和录制声音

1// libs: -lpulse 
2#include <stdio.h>
3#include <stdlib.h>
4#include <errno.h>
5#include <string.h>
6#include <stdbool.h>
7#include <pulse/pulseaudio.h>
8
9#ifdef DEBUG
10    #define LOG(...) printf(__VA_ARGS__)
11#else
12    #define LOG(...)
13#endif
14
15typedef enum Method
16{
17    METHOD_PLAY,
18    METHOD_RECORD
19}Method;
20
21typedef struct UserData
22{
23    pa_context* context;
24    pa_stream* stream;
25    pa_mainloop_api* api;
26    FILE* fp;
27    Method method;
28}UserData;
29
30static void mainloop_quit(UserData* userdata, int ret);
31static void context_state_callback(pa_context* context, void* userdata) ;
32static void stream_write_callback(pa_stream* stream, size_t length, void* userdata);
33static void stream_read_callback(pa_stream* stream, size_t length, void* userdata);
34static void stream_drain_complete(pa_stream* stream, int success, void* userdata);
35static void context_drain_complete(pa_context* c, void* userdata);
36
37int main(int argc, char* argv[]) 
38{
39    // 参数检查
40    if(argc != 3)
41    {
42        printf("pademo [play|record] <sound-file>\n");
43        return EXIT_FAILURE;
44    }
45
46    // 创建一个线程,并在该线程中创建 mainloop
47    pa_mainloop* mainloop = pa_mainloop_new();
48    if(mainloop == NULL)
49    {
50        fprintf(stderr, "pa_threaded_mainloop_new failed.\n");
51        return EXIT_FAILURE;
52    }
53
54    // 获取API
55    pa_mainloop_api* api = pa_mainloop_get_api(mainloop);
56    if(api == NULL)
57    {
58        pa_mainloop_free(mainloop);
59        fprintf(stderr, "pa_threaded_mainloop_get_api failed.\n");
60        return EXIT_FAILURE;
61    }
62
63    // 创建上下文
64    pa_context* context = pa_context_new(api, "demo");
65    if(context == NULL)
66    {
67        pa_mainloop_free(mainloop);
68        fprintf(stderr, "pa_context_new failed.\n");
69        return EXIT_FAILURE;
70    }
71
72    UserData data;
73    if(strcmp(argv[1],"play") == 0)
74    {
75        data.fp = fopen(argv[2], "rb");
76        data.method = METHOD_PLAY;
77    } 
78    else if(strcmp(argv[1], "record") == 0)
79    {
80        data.fp = fopen(argv[2], "wb");
81        data.method = METHOD_RECORD;
82    }
83    else
84    {
85        fprintf(stderr, "Unknown method %s\n", argv[1]);
86        // 释放
87        pa_context_unref(context);
88        pa_mainloop_free(mainloop);
89        return EXIT_FAILURE;
90    }
91
92    // 设置状态变化的回调函数,这是主入口
93    data.context = context;
94    data.api = api;
95    pa_context_set_state_callback(context, context_state_callback, (void*)(&data));
96
97    // 开始建立连接
98    if(pa_context_connect(context, NULL, PA_CONTEXT_NOFAIL, NULL) < 0)
99    {
100        pa_context_unref(context);
101        pa_mainloop_free(mainloop);
102        fprintf(stderr, "pa_context_connect failed.\n");
103        return EXIT_FAILURE;
104    }
105    
106    // 运行mainloop
107    int ret = pa_mainloop_run(mainloop, NULL);
108
109    // 退出
110    pa_context_unref(context);
111    pa_mainloop_free(mainloop);
112    return ret;
113}
114
115// 退出主循环
116static void mainloop_quit(UserData* userdata, int ret)
117{
118    LOG("mainloop_quit\n");
119    userdata->api->quit(userdata->api, ret);
120}
121
122// 状态变化的回调函数
123static void context_state_callback(pa_context* context, void* userdata) 
124{
125    UserData* data = (UserData*)(userdata);
126    pa_context_state_t state = pa_context_get_state(context);
127    switch (state) 
128    {
129    case PA_CONTEXT_READY: // 上下文就绪
130    {
131        LOG("PA_CONTEXT_READY\n");
132
133        // 创建spec
134        pa_sample_spec sampleSpec;
135        sampleSpec.rate = 44100;
136        sampleSpec.format = PA_SAMPLE_S16LE;
137        sampleSpec.channels = 2;
138
139        // 创建channel map
140        pa_channel_map channelMap;
141        pa_channel_map_init_stereo(&channelMap);
142
143        // 创建stream
144        pa_stream* stream = pa_stream_new(context, "demo-stream", &sampleSpec, &channelMap);
145        data->stream = stream;
146
147        if(data->method == METHOD_PLAY) // 播放
148        {
149            pa_stream_connect_playback(stream, NULL, NULL, PA_STREAM_NOFLAGS, NULL, NULL);
150            pa_stream_set_write_callback(stream, stream_write_callback, userdata);
151        }
152        else if(data->method == METHOD_RECORD) // 录音
153        {
154            pa_stream_connect_record(stream, NULL, NULL, PA_STREAM_NOFLAGS);
155            pa_stream_set_read_callback(stream, stream_read_callback, userdata);
156        }
157        break;
158    }
159
160    case PA_CONTEXT_TERMINATED: // 结束
161        LOG("PA_CONTEXT_TERMINATED\n");
162        mainloop_quit(data, EXIT_SUCCESS);
163        break;
164
165    default:
166        LOG("context state %d\n", state);
167    }
168}
169
170// 播放的回调
171static void stream_write_callback(pa_stream* stream, size_t length, void* userdata)
172{
173    UserData* data = (UserData*)(userdata);
174    void* buffer;
175    while(true)
176    {
177        // 给buffer分配空间,不需要手动释放
178        pa_stream_begin_write(stream, &buffer, &length); 
179        
180        // 读取文件,写入stream
181        length = fread(buffer, 1, length, data->fp);
182        pa_stream_write(stream, buffer, length, NULL, 0, PA_SEEK_RELATIVE); // 会自动释放buffer
183        LOG("play %zu bytes\n", length);
184
185        // 文件读取完毕
186        if(feof(data->fp))
187        {
188            pa_stream_cancel_write(stream);
189            pa_stream_set_write_callback(stream, NULL, NULL); //清除回调
190            pa_operation* o = pa_stream_drain(stream, stream_drain_complete, data); // 设置播放完毕时的回调
191            if(o == NULL)
192            {
193                mainloop_quit(data, EXIT_FAILURE);
194            }
195            pa_operation_unref(o);
196            break;
197        }
198    }
199}
200
201// 录音的回调
202static void stream_read_callback(pa_stream* stream, size_t length, void* userdata)
203{
204    UserData* data = (UserData*)(userdata);
205    const void *buffer;
206    while(pa_stream_readable_size(stream) > 0)
207    {
208        pa_stream_peek(stream, &buffer, &length);
209        if(buffer == NULL || length <= 0)
210        {
211            continue;
212        }
213
214        fwrite(buffer, length, 1, data->fp);
215        fflush(data->fp);
216        LOG("record %zu bytes\n", length);
217        pa_stream_drop(stream);
218    }
219}
220
221// 播放完毕的回调
222static void stream_drain_complete(pa_stream* stream, int success, void* userdata) 
223{
224    (void)(success);
225    LOG("stream_drain_complete\n");
226    UserData* data = (UserData*)(userdata);
227
228    // 释放stream
229    pa_stream_disconnect(stream); 
230    pa_stream_unref(stream);
231    data->stream = NULL;
232
233    // 设置上下文结束的回调
234    pa_operation* o = pa_context_drain(data->context, context_drain_complete, NULL);
235    if (o == NULL)
236    {
237        pa_context_disconnect(data->context);
238    }
239    else 
240    {
241        pa_operation_unref(o);
242    }
243}
244
245// 上下文结束的回调
246static void context_drain_complete(pa_context* context, void* userdata)
247{
248    (void)(userdata);
249    LOG("context_drain_complete\n");
250    pa_context_disconnect(context);
251}